package com.quectel.util.client.mongo;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ReflectUtil;
import com.mongodb.client.result.UpdateResult;
import lombok.Getter;
import org.bson.Document;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.AggregationResults;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;

import java.util.*;

/**
 * MongoClient主要是封装MongoTemplate
 *
 * @author: caolu
 * @email: louis.cao@quectel.com
 * @date: 2023-04-25 19:27:00
 */
public class MongoClient {
    @Getter
    private MongoTemplate mongoTemplate;

    /**
     * 无参构造
     */
    private MongoClient() {

    }

    public MongoClient(MongoTemplate mongoTemplate) {
        this.mongoTemplate = mongoTemplate;
    }


    /**
     * 是否已经初始化配置
     *
     * @return
     */
    public boolean isReady() {
        return this.mongoTemplate != null;
    }


    /**
     * 插入对象 需要有@Document注解
     *
     * @param basePojo
     * @param <T>
     * @return
     */
    public <T extends MongoBaseModel> T add(T basePojo) {
        return mongoTemplate.insert(basePojo);
    }

    /**
     * 插入对象并指定表名
     *
     * @param basePojo
     * @param collectionName
     * @param <T>
     * @return
     */
    public <T extends MongoBaseModel> T add(T basePojo, String collectionName) {
        return insert(basePojo, collectionName);
    }

    /**
     * 插入对象并指定表名
     *
     * @param objectToSave
     * @param collectionName
     * @param <T>
     * @return
     */
    public <T extends MongoBaseModel> T insert(T objectToSave, String collectionName) {
        return mongoTemplate.insert(objectToSave, collectionName);
    }

    /**
     * 批量插入
     *
     * @param batchToSave
     * @param collectionName
     * @param <T>
     * @return
     */
    public <T extends MongoBaseModel> Collection<T> insertMany(Collection<T> batchToSave, String collectionName) {
        return mongoTemplate.insert(batchToSave, collectionName);
    }

    /**
     * 按照id更新
     *
     * @param basePojo
     * @param update
     * @param collectionName
     * @param <T>
     * @return
     */
    public <T extends MongoBaseModel> UpdateResult update(T basePojo, Update update, String collectionName) {
        Query query = new Query();
        query.addCriteria(Criteria.where("_id").is(ReflectUtil.getFieldValue(basePojo, "id")));
        return mongoTemplate.updateFirst(query, update, collectionName);
    }

    /**
     * 按照条件批量更新
     *
     * @param query
     * @param update
     * @param collectionName
     * @return
     */
    public UpdateResult update(Query query, Update update, String collectionName) {
        return mongoTemplate.updateMulti(query, update, collectionName);
    }

    /**
     * 按照id删除
     *
     * @param id
     * @param collectionName
     */
    public void delete(Long id, String collectionName) {
        Query query = new Query();
        query.addCriteria(Criteria.where("_id").is(id));
        mongoTemplate.remove(query, collectionName);
    }

    /**
     * 按照条件删除
     *
     * @param basePojo
     * @param collectionName
     * @param <T>
     */
    public <T extends MongoBaseModel> void delete(T basePojo, String collectionName) {
        mongoTemplate.remove(basePojo, collectionName);
    }

    /**
     * 按照条件删除
     *
     * @param query
     * @param collectionName
     */
    public void remove(Query query, String collectionName) {
        mongoTemplate.remove(query, collectionName);
    }

    /**
     * 按照id查询
     *
     * @param id
     * @param collectionName
     * @param clazz
     * @param <T>
     * @return
     */
    public <T extends MongoBaseModel> T findOne(Long id, String collectionName, Class<T> clazz) {
        return mongoTemplate.findById(id, clazz, collectionName);
    }

    /**
     * 按照条件找到一条数据
     *
     * @param clazz
     * @param query
     * @param collectionName
     * @param <T>
     * @return
     */
    public <T extends MongoBaseModel> T findOneData(Class<T> clazz, Query query, String collectionName) {
        return mongoTemplate.findOne(query, clazz, collectionName);
    }

    /**
     * 查询所有记录
     *
     * @param clazz
     * @param collectionName
     * @param <T>
     * @return
     */
    public <T extends MongoBaseModel> List<T> findAll(Class<T> clazz, String collectionName) {
        return mongoTemplate.findAll(clazz, collectionName);
    }

    /**
     * 按照条件找到多个记录
     *
     * @param query
     * @param clazz
     * @param collectionName
     * @param <T>
     * @return
     */
    public <T extends MongoBaseModel> List<T> find(Query query, Class<T> clazz, String collectionName) {
        return mongoTemplate.find(query, clazz, collectionName);
    }

    /**
     * 分页方法
     *
     * @param query
     * @param pageNo   从0开始
     * @param pageSize
     * @param sort     null不排序
     * @param clazz
     * @return
     */
    public <T extends MongoBaseModel> List<T> page(Query query,
                                                   int pageNo,
                                                   int pageSize,
                                                   Sort sort,
                                                   Class<T> clazz, String collectionName) {
        if (query != null) {
            query.with(PageRequest.of(pageNo, pageSize));
            if (sort != null) {
                query.with(sort);
            }
        }
        List<T> result = mongoTemplate.find(query, clazz, collectionName);
        return result == null ? new ArrayList<>() : result;
    }

    /**
     * 按照条件查询数量
     *
     * @param query
     * @param collectionName
     * @return
     */
    public long count(Query query, String collectionName) {
        return mongoTemplate.count(query, collectionName);
    }

    /**
     * 去重查询数量
     *
     * @param criteria
     * @param collectionName
     * @param field          $deviceDto._id 或者 deviceDto.id
     * @return
     */
    public int countDistinct(Criteria criteria, String collectionName, String field) {

        AggregationResults<Document> aggregate = mongoTemplate.aggregate(
                Aggregation.newAggregation(
                        Aggregation.match(criteria),
                        Aggregation.group(field),
                        Aggregation.group().count().as("total")
                ),
                collectionName,
                Document.class
        );
        List<Document> mappedResults = aggregate.getMappedResults();
        for (Document mappedResult : mappedResults) {
            return Convert.convert(Integer.class, mappedResult.get("total"));
        }
        return 0;
    }

    /**
     * 按照条件查找最大值
     *
     * @param query
     * @param clazz
     * @param collectionName
     * @param fieldName
     * @param <T>
     * @return
     */
    public <T extends MongoBaseModel> T max(Query query, Class<T> clazz, String collectionName, String fieldName) {

        return mongoTemplate.findOne(query.with(Sort.by(Sort.Order.desc(fieldName))).skip(0).limit(1), clazz, collectionName);
    }

    /**
     * 按照条件查找最小值
     *
     * @param query
     * @param clazz
     * @param collectionName
     * @param fieldName
     * @param <T>
     * @return
     */
    public <T extends MongoBaseModel> T min(Query query, Class<T> clazz, String collectionName, String fieldName) {

        return mongoTemplate.findOne(query.with(Sort.by(Sort.Direction.ASC, fieldName)).skip(0).limit(1), clazz, collectionName);
    }

    /**
     * 聚合
     *
     * @param aggregation
     * @param collectionName
     * @param var2
     * @param <O>
     * @return
     */
    public <O> AggregationResults<O> aggregate(Aggregation aggregation, String collectionName, Class<O> var2) {

        return mongoTemplate.aggregate(aggregation, collectionName, var2);
    }

    /**
     * 分组统计
     *
     * @param criteria
     * @param field          $deviceDto._id 或者 deviceDto.id
     * @param collectionName
     * @return 需要注意key可能为 null
     */
    public Map<String, Integer> groupCountBy(Criteria criteria, String field, String collectionName) {
        Map<String, Integer> result = new LinkedHashMap<>();

        AggregationResults<Document> aggregate = mongoTemplate.aggregate(
                Aggregation.newAggregation(
                        Aggregation.match(criteria),
                        Aggregation.group(field).count().as("total")
                ),
                collectionName,
                Document.class
        );
        List<Document> mappedResults = aggregate.getMappedResults();
        for (Document mappedResult : mappedResults) {
            result.put(Convert.convert(String.class, mappedResult.get("_id")), Convert.convert(Integer.class, mappedResult.get("total")));
        }
        return result;
    }

    /**
     * 查询列表
     *
     * @param query
     * @param clazz
     * @param collectionName
     * @param <T>
     * @return
     */
    public <T extends MongoBaseModel> Collection<T> queryList(Query query,
                                                              Class<T> clazz, String collectionName) {
        return mongoTemplate.find(query, clazz, collectionName);
    }

    /**
     * 按照条件查询数量
     *
     * @param query
     * @param collectionName
     * @return
     */
    public long total(Query query, String collectionName) {
        return mongoTemplate.count(query, collectionName);
    }

    /**
     * 按照条件查找第一条
     *
     * @param query
     * @param entityClass
     * @param collectionName
     * @param <T>
     * @return
     */
    public <T extends MongoBaseModel> T queryOne(Query query, Class<T> entityClass, String collectionName) {
        return mongoTemplate.findOne(query, entityClass, collectionName);
    }
}
